#include <stdio.h>
#include <stdlib.h>
#include <string.h>

API_FILELOCAL const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

API_FILELOCAL inline char base64_inv(const char input) {
	if (input >= 'A' && input <= 'Z')
		return (input - 'A');
	
	if (input >= 'a' && input <= 'z')
		return (input - 'a' + 26);
	
	if (input >= '0' && input <= '9')
		return (input - '0' + 52);
	
	if (input == '+')
		return 62;

	if (input == '/')
		return 63;

	if (input == '=')
		return 0;
	
	return 0;
}

/**
 * Encodes an input string into a base64 sequence.
 *
 * @param input Input string
 * @param input_len Length of input string
 * @param output_len Pointer to a size_t with the length of output
 * @return Base64 encoded string.
 */
API_LIBLOCAL char *base64_encode(const char *input, size_t input_len, size_t *output_len) {
	char buf[4];
	char *output;
	size_t i, j, pad;

	*output_len = input_len * 4 / 3 + (3 - input_len) % 4;

	output = malloc(*output_len + 1);

	pad = 0;
	for (i = 0, j = 0; i < input_len; i += 3, j += 4) {
		if (input_len - i < 3) {
			pad = 3 - (input_len - i);
		}

		buf[0] = base64_chars[(input[i]) >> 2];
		buf[1] = base64_chars[((input[i] & 0x03) << 4) + ((input[i+1]) >> 4)];
		buf[2] = base64_chars[((input[i+1] & 0x0F) << 2) + ((input[i+2]) >> 6)];
		buf[3] = base64_chars[(input[i+2] & 0x3F)];

		for (; pad > 0; pad--) {
			buf[4 - pad] = '=';
		}

		memcpy(output + j, buf, 4);
	}

	output[*output_len] = '\0';

	return output;
}

/**
 * Decodes an input base64 string into a string.
 *
 * @param input Input string, base64 encoded
 * @param input_len Length of input string
 * @param output_len Pointer to a size_t with the length of output
 * @return Original (base64-decoded) string
 */
API_LIBLOCAL char *base64_decode(const char *input, size_t input_len, size_t *output_len) {
	char buf[3];
	char *output;
	size_t i, j;

	*output_len = input_len * 3 / 4;

	/* Alloc one extra byte to ensure null-termination even for
	 * decoded data... */
	output = malloc(*output_len + 1);
	memset(output + *output_len - 2, 0, 3);

	for (i = 0, j = 0; i < input_len; i += 4, j += 3) {
		buf[0] = (base64_inv(input[i]) << 2) + (base64_inv(input[i+1]) >> 4);
		buf[1] = ((base64_inv(input[i+1]) & 0x0F) << 4) + ((base64_inv(input[i+2]) & 0xFC) >> 2);
		buf[2] = ((base64_inv(input[i+2]) & 0x03) << 6) + (base64_inv(input[i+3]));

		memcpy(output + j, buf, 3);
	}

	return output;
}
